package conchmusic.conchmusicclient.util;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import conchmusic.conchmusicclient.pojo.Album;
import conchmusic.conchmusicclient.pojo.Artist;
import conchmusic.conchmusicclient.pojo.Comment;
import conchmusic.conchmusicclient.pojo.Song;
import conchmusic.conchmusicclient.pojo.SongList;
import conchmusic.conchmusicclient.pojo.TimeStamps;
import conchmusic.conchmusicclient.pojo.UpdatedData;
import conchmusic.conchmusicclient.pojo.User;

/**
 * Utility class for parsing JSON strings to bean objects.
 *
 * @author zhixu
 */
public class JsonParser {

    /**
     * Convert a JSONArray to a List. Result is stored in the second parameter.
     *
     * @param jsonAray
     * @param list     The result List.
     * @throws JSONException
     */
    private void JsonArrayToList(JSONArray jsonAray, List<Object> list) throws JSONException {
        for (int i = 0; i < jsonAray.length(); i++)
            list.add(jsonAray.get(i));
    }

    /**
     * Convert a JSONObject to a bean Object.
     * When there is a List field in the bean object, this method can automatically parse it
     * if the List parameter is an elementary type or its wrapped type or String type,
     * otherwise it cannot, and you must apply this method on the List elements in a recursive style.
     *
     * @param jsonObj
     * @param bean    The result bean object.
     * @return The result bean object.
     */
    private Object JsonObjToBeanObj(JSONObject jsonObj, Object bean) {
        Field[] fields = bean.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (!jsonObj.has(field.getName()))
                continue;
            field.setAccessible(true);
            try {
                String type = field.getType().getName();
                switch (type) {
                    case "java.util.List":
                        List<Object> list = new ArrayList<Object>();
                        JsonArrayToList((JSONArray) jsonObj.get(field.getName()), list);
                        field.set(bean, list);
                        break;
                    case "java.lang.String":
                    case "java.lang.Integer":
                    case "int":
                    case "long":
                    case "java.lang.Long":
                        field.set(bean, jsonObj.get(field.getName()));
                        break;
                    default:
                        Object obj = Class.forName(type).newInstance();
                        JsonObjToBeanObj((JSONObject) jsonObj.get(field.getName()), obj);
                        field.set(bean, obj);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return bean;
    }

    /**
     * Get the "type" field of a JSON string.
     *
     * @param jsonStr
     * @return a string of "type" field, or null if there is no "type" field.
     * @throws JSONException
     */
    public String typeOf(String jsonStr) throws JSONException {
        JSONObject jsonobj = new JSONObject(jsonStr);
        if (!jsonobj.has("type"))
            return null;

        String type = jsonobj.getString("type");
        return type;
    }

    /**
     * Parse a JSON string to a User object.
     *
     * @param jsonStr
     * @return a User object, or null if the JSON string is not of type "user"
     * @throws JSONException
     */
    public User parseUser(String jsonStr) throws JSONException {
        JSONObject jsonObj = new JSONObject(jsonStr);
        String type = jsonObj.getString("type");
        if (!"user".equals(type))
            return null;

        User resUser = new User();
        JsonObjToBeanObj(jsonObj.getJSONObject("user"), resUser);

        return resUser;
    }

    /**
     * Parse a JSON string to a TimeStamps object
     *
     * @param jsonStr
     * @return a TimeStamps object, or null if the JSON string is not of type "timeStamps"
     * @throws JSONException
     */
    public TimeStamps parseTimeStamps(String jsonStr) throws JSONException {
        JSONObject jsonObj = new JSONObject(jsonStr);
        String type = jsonObj.getString("type");
        if (!"timeStamps".equals(type))
            return null;

        TimeStamps resTimeStamps = new TimeStamps();
        JsonObjToBeanObj(jsonObj.getJSONObject("timeStamps"), resTimeStamps);

        return resTimeStamps;
    }

    /**
     * Parse a JSON string to a SongList object
     *
     * @param jsonStr
     * @return a SongList object, or null if the JSON string is not of type "songList"
     * @throws JSONException
     */
    public SongList parseSongList(String jsonStr) throws JSONException {
        JSONObject jsonObj = new JSONObject(jsonStr);
        String type = jsonObj.getString("type");
        if (!"songList".equals(type))
            return null;

        SongList resSongList = (SongList) JsonObjToBeanObj(jsonObj.getJSONObject("songList"), new SongList());
        return resSongList;
    }

    /**
     * Parse a JSON string to a UpdatedData object.
     *
     * @param jsonStr
     * @return a UpdatedData object, or null if the JSON string is not of type "updatedData"
     * @throws JSONException
     */
    public UpdatedData parseUpdatedData(String jsonStr) throws JSONException {
        JSONObject jsonObj = new JSONObject(jsonStr);
        String type = jsonObj.getString("type");
        if (!"updatedData".equals(type))
            return null;

        JSONObject updatedDataJO = jsonObj.getJSONObject("updatedData");
        JSONObject userJO = updatedDataJO.getJSONObject("user");
        JSONArray songListsJA = updatedDataJO.getJSONArray("songLists");
        JSONArray songsJA = updatedDataJO.getJSONArray("songs");

        UpdatedData resUpdatedData = new UpdatedData();
        resUpdatedData.setUser((User) JsonObjToBeanObj(userJO, new User()));
        resUpdatedData.setSongLists(new ArrayList<SongList>());
        resUpdatedData.setSongs(new ArrayList<Song>());
        for (int i = 0; i < songListsJA.length(); i++) {
            JSONObject songListJO = songListsJA.getJSONObject(i);
            SongList songList = (SongList) JsonObjToBeanObj(songListJO, new SongList());
            resUpdatedData.getSongLists().add(songList);
        }
        for (int i = 0; i < songsJA.length(); i++) {
            JSONObject songJO = songsJA.getJSONObject(i);
            Song song = (Song) JsonObjToBeanObj(songJO, new Song());
            resUpdatedData.getSongs().add(song);
        }

        return resUpdatedData;
    }

    /**
     * Parse a JSON string to an Artist object.
     *
     * @param jsonStr
     * @return an Artist object, or null if the JSON string is not of type "artist"
     * @throws JSONException
     */
    public Artist parseArtist(String jsonStr) throws JSONException {
        JSONObject jsonObj = new JSONObject(jsonStr);
        String type = jsonObj.getString("type");
        if (!"artist".equals(type))
            return null;

        Artist resArtist = (Artist) JsonObjToBeanObj(jsonObj.getJSONObject("artist"), new Artist());
        return resArtist;
    }

    /**
     * Parse a JSON string to an Album object.
     *
     * @param jsonStr
     * @return an Album object, or null if the JSON string is not of type "artist"
     * @throws JSONException
     */
    public Album parseAlbum(String jsonStr) throws JSONException {
        JSONObject jsonObj = new JSONObject(jsonStr);
        String type = jsonObj.getString("type");
        if (!"album".equals(type))
            return null;

        Album resAlbum = (Album) JsonObjToBeanObj(jsonObj.getJSONObject("album"), new Album());
        return resAlbum;
    }

    /**
     * Parse a JSON string to a Comment object.
     *
     * @param jsonStr
     * @return a Comment object, or null if the JSON string is not of type "comment"
     * @throws JSONException
     */
    public Comment parseComment(String jsonStr) throws JSONException {
        JSONObject jsonObj = new JSONObject(jsonStr);
        String type = jsonObj.getString("type");
        if (!"comment".equals(type))
            return null;

        Comment resComment = (Comment) JsonObjToBeanObj(jsonObj.getJSONObject("comment"), new Comment());
        return resComment;
    }

    /**
     * Parse a JSON string to a Comment List.
     *
     * @param jsonStr
     * @return a List<Comment>, or null if the JSON string is not of type "comments"
     * @throws JSONException
     */
    public List<Comment> parseComments(String jsonStr) throws JSONException {
        JSONObject jsonObj = new JSONObject(jsonStr);
        String type = jsonObj.getString("type");
        if (!"comments".equals(type))
            return null;

        JSONArray commentJA = jsonObj.getJSONArray("comments");
        List<Comment> resComments = new ArrayList<Comment>();

        for (int i = 0; i < commentJA.length(); i++) {
            JSONObject commentJO = commentJA.getJSONObject(i);
            Comment comment = (Comment) JsonObjToBeanObj(commentJO, new Comment());
            resComments.add(comment);
        }

        return resComments;
    }

    /**
     * Parse a JSON string to a Song List.
     *
     * @param jsonStr
     * @return a List<Song>, or null if the JSON string is not of type "songs"
     * @throws JSONException
     */
    public List<Song> parseSongs(String jsonStr) throws JSONException {
        JSONObject jsonObj = new JSONObject(jsonStr);
        String type = jsonObj.getString("type");
        if (!"songs".equals(type))
            return null;

        JSONArray songsJA = jsonObj.getJSONArray("songs");
        List<Song> resSongs = new ArrayList<Song>();

        for (int i = 0; i < songsJA.length(); i++) {
            JSONObject songJO = songsJA.getJSONObject(i);
            Song song = (Song) JsonObjToBeanObj(songJO, new Song());
            resSongs.add(song);
        }

        return resSongs;
    }

    /**
     * Parse a JSON string to a ValidInfo message string
     *
     * @param jsonStr
     * @return a String, or null if the JSON string is not of type "validInfo"
     * @throws JSONException
     */
    public String parseValidInfo(String jsonStr) throws JSONException {
        JSONObject jsonObj = new JSONObject(jsonStr);
        String type = jsonObj.getString("type");
        if (!"validInfo".equals(type))
            return null;

        return jsonObj.getString("msg");
    }

    /**
     * Parse a JSON string to a InvalidInfo message string
     *
     * @param jsonStr
     * @return a String, or null if the JSON string is not of type "invalidInfo"
     * @throws JSONException
     */
    public String parseInvalidInfo(String jsonStr) throws JSONException {
        JSONObject jsonObj = new JSONObject(jsonStr);
        String type = jsonObj.getString("type");
        if (!"invalidInfo".equals(type))
            return null;

        return jsonObj.getString("msg");
    }
}
